﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace XNADota.Graphics
{
    public enum ERenderState : uint
    {
        // Render state mode
        Opaque = 0,
        Translucent = 1,
        Transparent = 2,
        Additive = 3,
        AdditiveAlpha = 4,
        ParticleAccumulationBuffer = 5,
        ShadowDarkening = 6,
        ThatCrazyBlendModeWeUsedForTheReflections = 7,
        BlendModeMask = 7,

        // Depth buffer states.
        DisableDepth = 8 * 0,
        EnableDepth = 8 * 1,
        DepthTestButNoWrites = 8 * 2,
        StencilShadowMode = 8 * 3,
        DepthBufferMask = 8 * 3,

        // CullMode
        NoCull = 32 * 0,
        CullClockWise = 32 * 1,
        CullCounterClockWise = 32 * 2,
        CullModeMask = 32 * 3,

        // FillMode
        Solid = 0,
        WireFrame = 128 * 1,
        Point = 128 * 2,
        FillModeMask = 128 * 3,

        Initialize = 7 | (8 * 3) | (32 * 3) | 128 * 3,
        None = 0,
    }

    class RenderStateManager
    {
        private ERenderState _PreviousState;

        private GraphicsDevice _GraphicsDevice;

        public void Initialize(GraphicsDevice GraphicsDevice)
        {
            _GraphicsDevice = GraphicsDevice;
            _PreviousState = ERenderState.Initialize;
            SetRenderStates(ERenderState.None);
        }

        private static RenderStateManager _Instance = null;
        public static RenderStateManager Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new RenderStateManager();
                return _Instance;
            }
        }

        public void SetRenderStates(ERenderState state)
        {
            // See what states have changed.
            ERenderState changes = state ^ _PreviousState;

            if (changes != 0)
            {
                RenderState renderState = _GraphicsDevice.RenderState;

                // Have any alpha blending states changed?
                if ((changes & ERenderState.BlendModeMask) != 0)
                {
                    switch (state & ERenderState.BlendModeMask)
                    {
                        case ERenderState.Opaque:
                            renderState.SeparateAlphaBlendEnabled = false;
                            renderState.AlphaTestEnable = false;
                            renderState.AlphaBlendEnable = false;
                            break;
                        case ERenderState.Translucent:
                            renderState.AlphaTestEnable = true;
                            renderState.AlphaFunction = CompareFunction.Greater;
                            renderState.ReferenceAlpha = 192;
                            renderState.AlphaBlendEnable = false;
                            renderState.SeparateAlphaBlendEnabled = false;
                            break;
                        case ERenderState.Transparent:
                            renderState.AlphaBlendEnable = true;
                            renderState.SourceBlend = Blend.SourceAlpha;
                            renderState.DestinationBlend = Blend.InverseSourceAlpha;
                            renderState.BlendFunction = BlendFunction.Add;

                            renderState.SeparateAlphaBlendEnabled = true;
                            renderState.AlphaSourceBlend = Blend.One;
                            renderState.AlphaDestinationBlend = Blend.One;
                            renderState.AlphaBlendOperation = BlendFunction.Add;

                            renderState.AlphaTestEnable = false;
                            break;
                        case ERenderState.Additive:
                            renderState.AlphaBlendEnable = true;
                            renderState.SourceBlend = Blend.One;
                            renderState.DestinationBlend = Blend.One;
                            renderState.BlendFunction = BlendFunction.Add;

                            renderState.SeparateAlphaBlendEnabled = false;
                            renderState.AlphaTestEnable = false;
                            break;
                        case ERenderState.AdditiveAlpha:
                            renderState.AlphaBlendEnable = true;
                            renderState.SourceBlend = Blend.SourceAlpha;
                            renderState.DestinationBlend = Blend.InverseSourceAlpha;
                            renderState.BlendFunction = BlendFunction.Add;

                            renderState.SeparateAlphaBlendEnabled = true;
                            renderState.AlphaSourceBlend = Blend.One;
                            renderState.AlphaDestinationBlend = Blend.One;
                            renderState.AlphaBlendOperation = BlendFunction.Add;

                            renderState.AlphaTestEnable = false;
                            break;
                        default:
                            throw new InvalidGraphicsException("Error in setting blend mode.");
                    }
                }

                // Have any depth buffer states changed?
                if ((changes & ERenderState.DepthBufferMask) != 0)
                {
                    switch (state & ERenderState.DepthBufferMask)
                    {
                        case ERenderState.DisableDepth:
                            renderState.DepthBufferEnable = false;
                            renderState.DepthBufferWriteEnable = false;
                            break;
                        case ERenderState.EnableDepth:
                            renderState.DepthBufferEnable = true;
                            renderState.DepthBufferWriteEnable = true;
                            break;
                        case ERenderState.DepthTestButNoWrites:
                            renderState.DepthBufferEnable = true;
                            renderState.DepthBufferWriteEnable = false;
                            break;
                        case ERenderState.StencilShadowMode:
                            break;
                        default:
                            throw new InvalidGraphicsException("Error in setting depth buffer.");
                    }
                }

                // Have any cull mode states changed?
                if ((changes & ERenderState.CullModeMask) != 0)
                {
                    switch (state & ERenderState.CullModeMask)
                    {
                        case ERenderState.NoCull:
                            renderState.CullMode = CullMode.None;
                            break;
                        case ERenderState.CullClockWise:
                            renderState.CullMode = CullMode.CullClockwiseFace;
                            break;
                        case ERenderState.CullCounterClockWise:
                            renderState.CullMode = CullMode.CullCounterClockwiseFace;
                            break;
                        default:
                            throw new InvalidGraphicsException("Error in setting cull mode.");
                    }
                }

                // Have any fill mode states changed?
                if ((changes & ERenderState.FillModeMask) != 0)
                {
                    switch (state & ERenderState.FillModeMask)
                    {
                        case ERenderState.Solid:
                            renderState.FillMode = FillMode.Solid;
                            break;
                        case ERenderState.WireFrame:
                            renderState.FillMode = FillMode.WireFrame;
                            break;
                        case ERenderState.Point:
                            renderState.FillMode = FillMode.Point;
                            break;
                        default:
                            throw new InvalidGraphicsException("Error in setting cull mode.");
                    }
                }

                _PreviousState = state;
            }
        }
    }
}